package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * @author He Kuntao
 * @desc 基于Redis的全局ID生成器
 * @date 2024/1/10 16:30
 */

@Component
public class RedisIdWorker {

    /**
     * 起始时间秒数，2000-1-1 00:00:00
     */
    private static final Long BEGIN_TIMESTAMP = 946684800L;
    /**
     * 序列号位数
     */
    public static final int BIT_MOVE_NUM = 32;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 基于Redis的全局ID生成器
     * 4B 时间戳  +  4B 序列号
     * 时间戳：正秒，需要定义起始基数
     *
     * @param key 当前业务ID生成器key
     * @return ID
     */
    public long nextId(String key) {
        /* 生成时间戳 */
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        /* 生成序列号 */
        // 建议key后拼接日期，防止一个key的序列号溢出；也方便统计的订单量
        // 若想分别统计年月日的订单，可以对date进行 yyyy:MM:dd 处理，Redis会自动分级
        // eg: incr:order:202001 (精确到月)
        String date = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        // 此处无需进行key提前创建，因为redis的incr命令会自动创建key
        Long count = stringRedisTemplate.opsForValue().increment(key + date);

        /* 拼接返回 */
        // 因为返回的是long类型，不直接使用字符串的拼接方式，而是使用位运算
        assert count != null;
        return timestamp << BIT_MOVE_NUM | count;
    }
}
